// Ofri Keidar 302933981 group 05

package controller;

import static org.junit.Assert.*;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import model.SystemDataManager;
import model.database.DBException;
import model.database.StoreDB;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import controller.SystemController;

/**
 * Class to test all of customer's transactions.
 * Class adds test record to database, and uses system's controller to run
 * the transactions and check the results.
 * IMPORTANT:
 * database must be empty before running tests, because expected values are
 * based on test records only
 */
public class CustomerControllerTester {
	
	/**
	 * Method that runs before the first test.
	 * This method enters to database the necessary test records.
	 * Test records are:
	 * 1. customer
	 * 2. user-name for customer
	 * 3. product
	 * 4. order
	 */
	@BeforeClass
	public static void setup() {

		// connection to database
		Connection connection = null;
		MysqlDataSource ds = new MysqlConnectionPoolDataSource();
		
    	// set server and database        	
    	ds.setServerName(TestCommons.SERVER_NAME);
    	ds.setDatabaseName(TestCommons.DB_NAME);

		try {
			
			// connect to database
			connection = ds.getConnection(TestCommons.DB_USER, TestCommons.DB_PWD);
			Statement statement = connection.createStatement();
			
			// create a customer record
			// create query string
			String query = String
					.format("INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', '%s', '%s', '%s')",
							TestCommons.CUSTOMERS_TABLE, TestCommons.NUMBER,
							TestCommons.NAME, TestCommons.USER_NAME, TestCommons.ADDRESS,
							TestCommons.PAYMENT_METHOD, TestCommons.TEST_NUMBER,
							TestCommons.TEST_NAME,
							TestCommons.TEST_CUSTOMER_USER_NAME,
							TestCommons.TEST_ADDRESS,
							TestCommons.TEST_PAYMENT_METHOD);

			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not add customer
				fail();
			}
			
			// set user-name for customer
			query = String.format(
					"INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s')",
					TestCommons.PASSWORDS_TABLE, TestCommons.USER_NAME, TestCommons.PASSWORD, TestCommons.PERMISSION,
					TestCommons.TEST_CUSTOMER_USER_NAME, TestCommons.TEST_PASSWORD, 
					CommonValues.permissions.CUSTOMER.ordinal());
			
			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not add customer user-name
				fail();
			}
			
			// create a product record
			// create query string
			query = String.format(
					"INSERT INTO %s (%s, %s) VALUES ('%s', '%s')",
					TestCommons.PRODUCTS_TABLE, TestCommons.NAME, TestCommons.PRICE,
					TestCommons.TEST_PRODUCT_NAME, TestCommons.TEST_PRODUCT_PRICE);

			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not add product
				fail();
			}

			// create order record
			// create query string
			query = String.format(
					"INSERT INTO %s (%s, %s, %s, %s, %s) VALUES (CURDATE(), '%s', '%s', '%s', '%s')",
					TestCommons.ORDERS_TABLE, TestCommons.DATE, TestCommons.ORDERING_CUSTOMER_NUMBER, TestCommons.NAME, 
					TestCommons.AMOUNT, TestCommons.TOTAL_SUM,
					TestCommons.TEST_NUMBER, TestCommons.TEST_PRODUCT_NAME, TestCommons.TEST_AMOUNT, 
					TestCommons.TEST_AMOUNT*TestCommons.TEST_PRODUCT_PRICE);
			
			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not add product
				fail();
			}
			
		} catch (SQLException e) {
			
			// check reason for error
			e.printStackTrace();
			
			// tests should fail in case of exception
			fail();
			
		} finally {
			
			// close connection
			if (connection != null) {
				try {
					
					connection.close();
					
				} catch (SQLException e) {

					// check reason for error
					e.printStackTrace();
					
					// tests should fail in case of exception
					fail();
				}
			}
		}
	}

	/**
	 * Method that after the last test.
	 * This method deletes from database all test records
	 * that were added in setup()
	 */
	@AfterClass
	public static void teardown() {

		// connection to database
		Connection connection = null;
		MysqlDataSource ds = new MysqlConnectionPoolDataSource();

		// set server and database
		ds.setServerName(TestCommons.SERVER_NAME);
		ds.setDatabaseName(TestCommons.DB_NAME);

		try {
			
			// connect to database
			connection = ds.getConnection(TestCommons.DB_USER, TestCommons.DB_PWD);
			Statement statement = connection.createStatement();
			
			// delete test customer record
			String query = String.format("DELETE FROM %s WHERE %s = %s", 
					TestCommons.CUSTOMERS_TABLE, TestCommons.NUMBER, TestCommons.TEST_NUMBER);
			
			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not delete customer
				fail();
			}
			
			// delete test customer's user record
			query = String.format("DELETE FROM %s WHERE %s = '%s'", 
					TestCommons.PASSWORDS_TABLE, TestCommons.USER_NAME, TestCommons.TEST_CUSTOMER_USER_NAME);
			
			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not delete customer's user
				fail();
			}
			
			// delete test product record
			query = String.format("DELETE FROM %s WHERE %s = '%s' ", 
					TestCommons.PRODUCTS_TABLE, TestCommons.NAME, TestCommons.TEST_PRODUCT_NAME);
			
			// execute query
			if (statement.executeUpdate(query) == 0) {

				// could not delete product
				fail();
			}
			
			// delete test order record
			query = String.format("TRUNCATE TABLE %s", TestCommons.ORDERS_TABLE);
			
			// execute query
			statement.executeUpdate(query);
			
		} catch (SQLException e) {
			
			// check reason for error
			e.printStackTrace();
			
			// tests should fail in case of exception
			fail();
			
		} finally { // close connection
			
			// close connection
			if (connection != null) {
				try {
					
					connection.close();
					
				} catch (SQLException e) {

					// check reason for error
					e.printStackTrace();
					
					// tests should fail in case of exception
					fail();
				}
			}
		}
	}
	
	/**
	 * Verifies that returned product is test product record
	 */
	@Test
	public void getProducts() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// get all products			
			List<Map<String, String>> result  = controller.getProducts();
			
			// build expected value
			List<Map<String, String>> expected = new LinkedList<Map<String,String>>();
			Map<String, String> firstProduct = new HashMap<String, String>();
			firstProduct.put(CommonValues.NAME, TestCommons.TEST_PRODUCT_NAME);
			firstProduct.put(CommonValues.PRICE, ""+TestCommons.TEST_PRODUCT_PRICE);
			expected.add(firstProduct);
			
			// compare values
			assertEquals(expected, result);
			
		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Asks for test product's price and verifies
	 * that its price is returned
	 */
	@Test
	public void getProductPrice() {
		
		try {
			
			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);
			
			// get test product's price 
			double result  = controller.getProductPrice(TestCommons.TEST_PRODUCT_NAME);
			
			// set expected value
			double expected = TestCommons.TEST_PRODUCT_PRICE;
			
			// compare values
			assertEquals(expected, result, 0);
			
		} catch (DBException e) {
			
			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}		
	}

	/**
	 * Asks price of product that does not exist in database 
	 * (relies on test product being in database).
	 * Verifies that NaN is returned
	 */
	@Test
	public void getNonExistingProductPrice() {
		
		try {
			
			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);
			
			// get test product's price
			Double price = controller.getProductPrice("blah blah");

			// check if error value
			if (Double.isNaN(price)) {
				
				assertEquals(true, true);
			}
			
		} catch (DBException e) {
			
			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}		
	}
	
	/**
	 * Verifies that returned order is test order record
	 */
	@Test
	public void getOrders() {
		
		try {
			
			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);
			
			// get all products			
			List<Map<String, String>> result  = controller.getOrders(TestCommons.TEST_NUMBER);
			
			// build expected value
			List<Map<String, String>> expected = new LinkedList<Map<String,String>>();			
			Map<String, String> testOrder = new HashMap<String, String>();
			testOrder.put(CommonValues.NUMBER, ""+TestCommons.TEST_INIT_ORDER_NUM);
			
			// get current date in format yyyy-mm-dd
			Calendar cal = Calendar.getInstance();
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
			String date = dateFormat.format(cal.getTime());
			testOrder.put(CommonValues.DATE, date);
			testOrder.put(CommonValues.ORDERING_CUSTOMER_NUMBER, ""+TestCommons.TEST_NUMBER);
			testOrder.put(CommonValues.NAME, TestCommons.TEST_PRODUCT_NAME);
			testOrder.put(CommonValues.AMOUNT, ""+TestCommons.TEST_AMOUNT);
			testOrder.put(CommonValues.TOTAL_SUM, ""+(TestCommons.TEST_AMOUNT*TestCommons.TEST_PRODUCT_PRICE));
			expected.add(testOrder);
						
			// compare values
			assertEquals(expected, result);
			
		} catch (DBException e) {
			
			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}		
	}

	/**
	 * Updates test customer's details, and verifies that
	 * transaction's return value is success.
	 * After updating for new details, sets the details
	 * back to default
	 */
	@Test
	public void updateCustomerDetailsTest() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// update customer details
			boolean couldUpdate = controller.updateCustomerDetails(TestCommons.TEST_NUMBER, "jimmy",
					"10 Rockstar Blv, Rocktown",
					"10-11-12");
			
			// update back the details
			controller
					.updateCustomerDetails(
							TestCommons.TEST_NUMBER,
							TestCommons.TEST_NAME,
							TestCommons.TEST_ADDRESS,
							TestCommons.TEST_PAYMENT_METHOD);
			
			// compare values
			assertEquals(true, couldUpdate);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Tries updating details of non-existing customer, 
	 * and verifies that transaction's return value is not success.
	 */
	@Test
	public void updateNonExistingCustomerDetailsTest() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// update customer details- CUSTOMER DOES NOT EXIST
			boolean couldUpdate = controller.updateCustomerDetails(TestCommons.TEST_NUMBER+1, "blah",
					"1 blah st, blahville", "10-11-12");
			
			// compare values 
			assertEquals(false, couldUpdate);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}

	/**
	 * Adds a new order for the test customer, and verifies that
	 * transaction's return value is success.
	 * After order is made, deletes it in order to maintain database's state
	 */
	@Test
	public void addNewOrder() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// add order
			boolean couldAdd = controller.addNewOrder(
					TestCommons.TEST_PRODUCT_NAME, TestCommons.TEST_AMOUNT,
					TestCommons.TEST_NUMBER);
			
			// delete order (for test-suite maintenance)
			
			// connection to database
			Connection connection = null;
			MysqlDataSource ds = new MysqlConnectionPoolDataSource();

			// set server and database
			ds.setServerName(TestCommons.SERVER_NAME);
			ds.setDatabaseName(TestCommons.DB_NAME);

			try {
				
				// delete order				
				connection = ds.getConnection(TestCommons.DB_USER, TestCommons.DB_PWD);				
				Statement statement = connection.createStatement();
				String query = String.format("DELETE FROM %s WHERE %s = %s", 
						TestCommons.ORDERS_TABLE, TestCommons.NUMBER, TestCommons.TEST_INIT_ORDER_NUM + 1);
			
				// execute query
				if (statement.executeUpdate(query) == 0) {
	
					// could not delete order
					fail();
				}
				
			} catch (SQLException e) {
				
				// check reason for error
				e.printStackTrace();
				
				// test should fail in case of exception
				fail();
				
			} finally {
				
				// close connection
				if (connection != null) {
					try {
						connection.close();
					} catch (SQLException e) {
						
						// check reason for error
						e.printStackTrace();
						
						// test should fails in case of exception
						fail();
					}
				}
			}
			
			// compare values 
			assertEquals(true, couldAdd);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Tries adding a new order for a customer that does not exist in database,
	 * and verifies that transaction's return value is not success
	 */
	@Test
	public void addOrderWrongCustomer() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// add order (ordering customer does not exist)
			boolean couldAdd = controller.addNewOrder(TestCommons.TEST_PRODUCT_NAME, TestCommons.TEST_AMOUNT, TestCommons.TEST_NUMBER + 1);
			
			// compare values
			assertEquals(false, couldAdd);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Tries adding a new order with a product that does not exist in database,
	 * and verifies that transaction's return value is not success
	 */
	@Test
	public void addOrderWrongProduct() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// add order (ordered product does not exist)
			boolean couldAdd = controller.addNewOrder("blah", TestCommons.TEST_AMOUNT, TestCommons.TEST_NUMBER);
			
			// compare values
			assertEquals(false, couldAdd);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Asks for test customer's user permission and verifies that
	 * customer permission is returned
	 */
	@Test
	public void getPermission() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// get permission of test customer-user
			CommonValues.permissions permission = controller.getUserPermission(
					TestCommons.TEST_CUSTOMER_USER_NAME,
					TestCommons.TEST_PASSWORD);
			
			// compare values
			assertEquals(CommonValues.permissions.CUSTOMER, permission);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Asks for test customer's name and verifies that TEST_NAME is returned
	 */
	@Test
	public void getUserRealName() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// get real name
			String name = controller.getUserRealName(TestCommons.TEST_CUSTOMER_USER_NAME);
			
			// compare values
			assertEquals(TestCommons.TEST_NAME, name);

		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception
			fail();
		}
	}
	
	/**
	 * Asks for real name of non-existing user record,
	 * and verifies that a DBException is thrown
	 */
	@Test
	public void getNonExistingUserRealName() {

		try {

			// create database
			StoreDB db = new StoreDB();
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// try getting real name of non-existing user
			try {
				
				controller.getUserRealName(TestCommons.TEST_CUSTOMER_USER_NAME+"blah");
				
			} catch (DBException e) {
				
				// an exception is expected
				assertEquals(true, true);
			}
			
		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception that is not resulted directly from transaction
			fail();
		}
	}

	/**
	 * Sets a new password for test customer record
	 * and verifies that returned value is True.
	 * After sets password, sets password back to default in order to maintain database's state 
	 */
	@Test
	public void setPassword() {
		
		try {

			// create database
			StoreDB db = new StoreDB();
			
			
			// create model
			SystemDataManager dataManager = new SystemDataManager(db);
			
			// create controller
			SystemController controller = new SystemController(dataManager);

			// set new password 
			String newPassword = TestCommons.TEST_PASSWORD + "a";
			boolean result = controller.setPassword(
					TestCommons.TEST_CUSTOMER_USER_NAME,
					TestCommons.TEST_PASSWORD, newPassword);
			
			// set password back to default
			controller.setPassword(TestCommons.TEST_CUSTOMER_USER_NAME, newPassword, TestCommons.TEST_PASSWORD);
			
			// an exception is expected
			assertEquals(true, result);
						
		} catch (DBException e) {

			// check reason for error
			e.printStackTrace();
			
			// test should fail in case of exception that is not resulted directly from transaction
			fail();
		}
	}
}